#include "input.h"

using namespace G3;

//==============================================================================
// Constructors and a destructor
//==============================================================================
G3::KEYBOARD::KEYBOARD () {
	KeyDown = NULL;
	NumKeys = 0;
	Modifiers = 0;
}

G3::MOUSE::MOUSE () {
    Mode = MOUSE_NORMAL;
    Sensitivity = MOUSE_DEF_SENSITIVITY;

    State = 0;

    ScreenWidth = 800;
    ScreenHeight = 600;
}

G3::INPUT::INPUT (FRAME *ParentFrame) {
    Frame = ParentFrame;
	LastElapsedTime = 0.0f;
}

G3::INPUT::~INPUT () {

}

//==============================================================================
// Updates the array of keys
//==============================================================================
void G3::KEYBOARD::Update () {
	KeyDown = SDL_GetKeyState (&NumKeys);
}

//==============================================================================
// Checks if a key is pressed
//==============================================================================
bool G3::KEYBOARD::IsKeyDown (int KeyID) {
	if (KeyDown != NULL && KeyID < NumKeys)
		return KeyDown [KeyID];
	return false;
}

//==============================================================================
// Poll SDL input device events and call appropriate handlers and callbacks
//==============================================================================
void G3::INPUT::GetInput () {
	ElapsedTime = 0.0f;

	if (Timer.StartCount == 0)
		Timer.Start ();
	else
		ElapsedTime = Timer.CheckTime ();

	ElapsedTime /= 1000.0f;
	LastElapsedTime = ElapsedTime;

    while (SDL_PollEvent (&event)) {
        switch (event.type) {
			// A key was released?
			case SDL_KEYUP:
				Keyboard.Update ();
				Keyboard.Modifiers = event.key.keysym.mod;
				SigKeyUp (this, &event.key.keysym, ElapsedTime);
				break;
            // A key was pressed?
            case SDL_KEYDOWN:
				Keyboard.Update ();
				Keyboard.Modifiers = event.key.keysym.mod;
				SigKeyDown (this, &event.key.keysym, ElapsedTime);
                break;

			// Someone teleported the mouse?
			case SDL_MOUSEMOTION:
				Mouse.State = SDL_GetMouseState (&Mouse.Position.x, &Mouse.Position.y);
				SigMouseMove (this, &event, ElapsedTime);
				break;
			// A mouse button was released?
			case SDL_MOUSEBUTTONUP:
				Mouse.State = SDL_GetMouseState (&Mouse.Position.x, &Mouse.Position.y);
			    SigMouseUp (this, &event, ElapsedTime);
				break;
			// A mouse button was pushed down?
			case SDL_MOUSEBUTTONDOWN:
				Mouse.State = SDL_GetMouseState (&Mouse.Position.x, &Mouse.Position.y);
                // Or was the mouse wheeled?
				if (event.button.button == SDL_BUTTON_WHEELUP) {
                    Mouse.Position.z += MOUSE_WHEEL_SENSITIVITY;
				    SigMouseWheel (this, &event, MOUSE_WHEEL_SENSITIVITY, ElapsedTime);
				} else if (event.button.button == SDL_BUTTON_WHEELDOWN) {
                    Mouse.Position.z -= MOUSE_WHEEL_SENSITIVITY;
				    SigMouseWheel (this, &event, -MOUSE_WHEEL_SENSITIVITY, ElapsedTime);
				} else
    				SigMouseDown (this, &event, ElapsedTime);
				break;

            // Ctrl+C? Alt+F4?
            case SDL_QUIT:
				SigExit ();
				SDL_Quit ();
                exit (0);
				// Should never get here..
                break;
        }
    }

	if (ConnUpdate.empty ())
		DefaultUpdateHandler (this, ElapsedTime);
	else
		SigUpdate (this, ElapsedTime);
}

//==============================================================================
// Sets mouse mode
//==============================================================================
void G3::MOUSE::SetMode (unsigned int NewMode) {
    Mode = NewMode;
}

//==============================================================================
// Sets mouse position
//==============================================================================
void G3::MOUSE::SetPosition (int x, int y, int z) {
    SDL_WarpMouse (x, y);
}

//==============================================================================
// Sets the screen size
//==============================================================================
void G3::MOUSE::SetScreenSize (int Width, int Height) {
     ScreenWidth = Width;
     ScreenHeight = Height;

     ScreenCenter = VEC3I (Width / 2, Height / 2, 0);
}

//==============================================================================
// Positions mouse at the center of the screen
//==============================================================================
void G3::MOUSE::Center () {
     ScreenCenter = VEC3I (ScreenWidth / 2, ScreenWidth / 2, 0);

     SetPosition (ScreenCenter.x, ScreenCenter.y);
}

//==============================================================================
// Updates the mouse position
//==============================================================================
VEC3I G3::MOUSE::UpdatePosition () {
     VEC3I v;

     // Normal mouse mode?
     if (Mode != MOUSE_NORMAL)
        return Position;

     SDL_GetMouseState (&v.x, &v.y);
     v.z = Position.z;

     // The mouse has not moved
     if (LastPosition == v)
         return Position;

     LastPosition = Position;
     Position = v;

     return Position;
}

//==============================================================================
// Returns Y and Z deltas for view update
//==============================================================================
VEC3F G3::MOUSE::GetDeltas () {
     VEC3I v;
     VEC3F a;

     // Mouse is not in Center mode?
     if (Mode != MOUSE_CENTER)
        return a;

     SDL_GetMouseState (&v.x, &v.y);
     v.z = Position.z;

     // The mouse has not moved
     if (v == ScreenCenter)
        return a;

     LastPosition = Position;
     Position = v;

     Center ();

     a.y = ((double) (ScreenCenter.x - Position.x)) / Sensitivity;
     a.z = ((double) (ScreenCenter.y - Position.y)) / Sensitivity;

     return a;
}

//==============================================================================
// Gets relative mouse position
//==============================================================================
VEC3I G3::MOUSE::GetPosition () {
    VEC3I v;

    SDL_GetRelativeMouseState (&v.x, &v.y);
    v.z = Position.z;

    return v;
}

//==============================================================================
// Rotates the given camera with our mouse
//==============================================================================
void G3::MOUSE::MouseLook (VIEW *cam) {
    if (Mode == MOUSE_CENTER) {
        VEC3F v = GetDeltas ();

        cam->DeltaRotate (v.y, -v.z);
    }
}

//==============================================================================
// Wrappers that set handlers to signals
//==============================================================================
G3::INPUT::SIG_CONNECTION G3::INPUT::doOnExit (EXIT_HANDLER aExitHandler) {
	Assert (aExitHandler, "INPUT::aExitHandler: ExitHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigExit.connect (aExitHandler);
	ConnExit.push_back (c);

	return c;
}

G3::INPUT::SIG_CONNECTION G3::INPUT::doOnKeyDown (KEY_HANDLER aKeyHandler) {
	Assert (aKeyHandler, "INPUT::doOnKeyDown: KeyHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigKeyDown.connect (aKeyHandler);
	ConnKeyDown.push_back (c);

	return c;
}

G3::INPUT::SIG_CONNECTION G3::INPUT::doOnKeyUp (KEY_HANDLER aKeyHandler) {
	Assert (aKeyHandler, "INPUT::doOnKeyUp: KeyHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigKeyUp.connect (aKeyHandler);
	ConnKeyUp.push_back (c);

	return c;
}

G3::INPUT::SIG_CONNECTION G3::INPUT::doOnMouseDown (MOUSE_HANDLER aMouseHandler) {
	Assert (aMouseHandler, "INPUT::doOnMouseDown: aMouseHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigMouseDown.connect (aMouseHandler);
	ConnMouseDown.push_back (c);

	return c;
}

G3::INPUT::SIG_CONNECTION G3::INPUT::doOnMouseUp (MOUSE_HANDLER aMouseHandler) {
	Assert (aMouseHandler, "INPUT::doOnMouseUp: aMouseHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigMouseUp.connect (aMouseHandler);
	ConnMouseUp.push_back (c);

	return c;
}

G3::INPUT::SIG_CONNECTION G3::INPUT::doOnMouseMove (MOUSE_HANDLER aMouseHandler) {
	Assert (aMouseHandler, "INPUT::doOnMouseMove: aMouseHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigMouseMove.connect (aMouseHandler);
	ConnMouseMove.push_back (c);

	return c;
}

G3::INPUT::SIG_CONNECTION G3::INPUT::doOnMouseWheel (WHEEL_HANDLER aWheelHandler) {
	Assert (aWheelHandler, "INPUT::doOnMouseWheel: aWheelHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigMouseWheel.connect (aWheelHandler);
	ConnMouseWheel.push_back (c);

	return c;
}

G3::INPUT::SIG_CONNECTION G3::INPUT::doOnUpdate (UPDATE_HANDLER aUpdateHandler) {
	Assert (aUpdateHandler, "INPUT::doOnUpdate: aUpdateHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigUpdate.connect (aUpdateHandler);
	ConnUpdate.push_back (c);

	return c;
}

//==============================================================================
// Calls ExitCallback, sends a quit message to SDL and shuts down the engine
//==============================================================================
void G3::INPUT::SendQuit () {
	SigExit ();
	SDL_Quit ();
	exit (0);
}

//==============================================================================
// Gets the elapsed time
//==============================================================================
float G3::INPUT::GetElapsedTime () {
	return LastElapsedTime;
}

